# -*- coding: utf-8 -*-

import re

import requests
from django.shortcuts import render, HttpResponse
from ldap3 import Server, Connection, ALL, SUBTREE, HASHED_SALTED_SHA, MODIFY_REPLACE

# 获取环境变量
from config import Config

cfg = Config()
# LDAP config
HOST = cfg.LDAP_HOST
ADMIN_DN = cfg.LDAP_ADMIN_DN
ADMIN_PASSWORD = cfg.LDAP_ADMIN_PASSWORD
SEARCH_BASE = cfg.LDAP_SEARCH_BASE
USE_SSL = False
if cfg.LDAP_USE_SSL == "True":
    USE_SSL = True

# Dingdng config
APPKEY = cfg.DING_APPKEY
APPSECRET = cfg.DING_APPSECRET

# 扫码之后跳转到的页面，需要在钉钉开发者后台的企业内应用配置里添加
REDIRECT_URI = cfg.DING_REDIRECT_URI


# Create your views here.
def index(request):
    print(HOST, ADMIN_DN, APPKEY, APPSECRET)
    '''主页'''
    ding_qrconnect = "https://oapi.dingtalk.com/connect/qrconnect?appid={}&response_type=code&scope=snsapi_login&state=STATE&redirect_uri={}".format(
        cfg.DING_APPKEY, REDIRECT_URI)
    return render(request, 'index.html', {"ding_qrconnect": ding_qrconnect})


def change_pwd(request):
    """
    :param request: 前端请求
    :return: 根据前端请求类型，如果是GET则返回修改密码页面，如果是POST说明是提交修改后的密码，则进行修改密码的逻辑
    在钉钉用户扫码登录并确认后，会302到你指定的redirect_uri，并向url参数中追加临时授权码code及state两个参数。
    """
    if request.method == "GET":
        unionid = ""
        try:
            code = request.GET["code"]  # 扫码之后带过来的url参数
        except Exception as e:
            print(e)
            return HttpResponse("本页面需要扫码进入，回到<a href='/'>首页</a>")
        unionid = get_unionid_bycode(code)
        print("从钉钉接口获取，扫码用户的 unionid: " + unionid)
        staff_dn, staff_name, staff_uid = ldap_search(unionid)
        print(staff_dn, staff_name)
        if not staff_dn:
            info = "您的信息在LDAP中不存在，请联系管理员。"
            return render(request, 'change_pwd_info.html', {"info": info})
        data = {"unionid": unionid, "staff_dn": staff_dn, "staff_name": staff_name, "staff_uid": staff_uid}
        return render(request, 'change_pwd.html', data)
    if request.method == "POST":
        pwd1 = request.POST.get("pwd1")
        pwd2 = request.POST.get("pwd2")
        # 获取隐藏数据
        staff_dn = request.POST.get("dn")
        staff_name = request.POST.get("staff_name")
        unionid = request.POST.get("unionid")
        staff_uid = request.POST.get("staff_uid")
        print("表单隐藏信息：", staff_dn, staff_name, unionid, staff_uid)

        res_dict = {
            "unionid": unionid,
            "staff_name": staff_name,
            "staff_dn": staff_dn,
            "staff_uid": staff_uid,
            "wrongpass": ""
        }

        # 开始验证密码格式
        if not pwd1:
            res_dict["wrongpass"] = "密码为空可不行哦"
            return render(request, 'change_pwd.html', res_dict)
        if len(pwd1) < 8:
            res_dict["wrongpass"] = "密码长度不足"
            return render(request, 'change_pwd.html', res_dict)
        if pwd1 != pwd2:
            res_dict["wrongpass"] = "两次输入密码不一致，请重新输入"
            return render(request, 'change_pwd.html', res_dict)
        if not check_password(pwd1):
            res_dict["wrongpass"] = "密码复杂度不足，请重新输入"
            return render(request, 'change_pwd.html', res_dict)

        # 修改密码
        if ldap_change_pwd(staff_dn, pwd1):
            print("密码修改成功！")
            return render(request, 'change_pwd_info.html', {"info": "密码修改成功！"})
        else:
            res_dict["wrongpass"] = "密码修改失败，请再试一次吧"
            return render(request, 'change_pwd.html', res_dict)


def check_password(password):
    """
    检查密码复杂度是否符合要求
    :param password:
    :return:
    """
    # 检查长度是否在8-20之间
    if not 8 <= len(password) <= 20:
        return False
    # 检查是否包含小写字母
    if not re.search(r'[a-z]', password):
        return False
    # 检查是否包含大写字母
    if not re.search(r'[A-Z]', password):
        return False
    # 检查是否包含数字
    if not re.search(r'[0-9]', password):
        return False
    # 检查是否包含特殊字符
    if not re.search(r'[!@#$%^&*()_+-=,.<>?]', password):
        return False
    return True


def ldap_search(unionid):
    """
    根据dingding扫码返回的用户的unionid，在LDAP中搜索到对应用户并返回信息
    :param unionid: 钉钉的unionid，在LDAP中，我们使用sn来存储unionid
    :return: dn, staff_name, staff_uid
    """
    dn = False
    staff_uid = 'Null'
    staff_name = 'Null'

    s = Server(HOST, get_info=ALL, use_ssl=USE_SSL)
    c = Connection(s, user=ADMIN_DN, password=ADMIN_PASSWORD, auto_bind=True)

    res = c.search(
        search_base=SEARCH_BASE,
        search_filter='(sn={})'.format(unionid),
        search_scope=SUBTREE,
        attributes=['sn', 'cn', 'uid', 'mail'],
        paged_size=5
    )

    if res:
        entry = c.response[0]
        print(entry)
        try:
            dn = entry['dn']
            attr_dict = entry['attributes']
            staff_name = attr_dict['cn'][0]
            staff_uid = attr_dict['uid'][0]
        except Exception as e:
            print(e)
    return dn, staff_name, staff_uid


def ldap_change_pwd(dn, pwd):
    from ldap3.utils.hashed import hashed
    """
    :param dn: 用户的dn
    :param pwd: 新密码
    :return:
    """
    s = Server(HOST, get_info=ALL, use_ssl=USE_SSL)
    c = Connection(s, user=ADMIN_DN, password=ADMIN_PASSWORD, auto_bind=True)

    hashed_password = hashed(HASHED_SALTED_SHA, pwd)
    changes = {
        'userPassword': [(MODIFY_REPLACE, [hashed_password])]
    }
    success = c.modify(dn, changes=changes)
    print(c.result)
    return success


def access_token():
    """
    获取企业内部应用的access_token，参考官方文档：https://open.dingtalk.com/document/orgapp-server/obtain-orgapp-token
    :return: access_token
    """
    url = 'https://oapi.dingtalk.com/gettoken?appkey=%s&appsecret=%s' % (APPKEY, APPSECRET)
    resp = requests.get(url=url)
    resp = resp.json()
    print(resp)
    return resp['access_token']


def get_timestamp():
    """
    :return: 返回当前时间戳，13位的毫秒
    """
    import time
    t = time.time()
    return int(round(t * 1000))


def get_ding_talk_signature(app_secret, utc_timestamp):
    """
    :param app_secret: 钉钉开发者文档创建的app密钥
    :param utc_timestamp: 官方文档中要签名的数据，单位是毫秒时间戳
    :return: 为所需要的签名值，此值为可逆的
    """
    import hmac
    from base64 import standard_b64encode

    digest = hmac.HMAC(key=app_secret.encode('utf8'),
                       msg=str(utc_timestamp).encode('utf8'),
                       digestmod=hmac._hashlib.sha256).digest()

    signature = standard_b64encode(digest).decode(encoding='utf8')
    return signature


def get_unionid_bycode(code):
    """
    根据sns临时授权码获取用户信息
    :param code: 钉钉用户扫码成功后，在跳转请求时附带的code参数
    :return: 返回用户unionid
    """
    from urllib.parse import urlencode

    ts = get_timestamp()
    signature = get_ding_talk_signature(APPSECRET, ts)
    print(ts, signature)

    queries = {
        "accessKey": APPKEY,
        "timestamp": ts,
        "signature": signature,
    }
    url = "https://oapi.dingtalk.com/sns/getuserinfo_bycode?{}".format(urlencode(queries))
    data = {"tmp_auth_code": code}
    headers = {"Content-Type": "application/json"}  # 指定提交的是json
    resp = requests.post(url=url, json=data, headers=headers)
    resp = resp.json()
    print(resp)
    try:
        unionid = resp['user_info']['unionid']
    except Exception as e:
        print(e)
        unionid = ''
    return unionid


if __name__ == '__main__':
    # print(ldap_change_pwd('uid=chenlaijun,ou=项目组,ou=交付中心,ou=People,dc=my-company,dc=com', 'ddddddd'))
    print(access_token())
    ts = get_timestamp()
    print(ts)
    sg = get_ding_talk_signature(APPSECRET, ts)
    print(sg)
